//
// Created on 2025/7/22.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#ifndef STREAMCORE_STREAM_BASE_H
#define STREAMCORE_STREAM_BASE_H

#include "hybrid/async_worker.h"
#include "hybrid/converter.h"
#include "hybrid/type/buffer.h"
#include "hybrid/type/object.h"
#include "hybrid/type/reference.h"
#include "native/stream.h"
#include <napi/native_api.h>

#define NAPI_CHECK(call, err_msg)                                                                                      \
    do {                                                                                                               \
        if ((call) != napi_ok) {                                                                                       \
            throw std::runtime_error(err_msg);                                                                         \
        }                                                                                                              \
    } while (0)

#define DEFINE_CLASS(JSName, PropertiesList)                                                                           \
    struct NapiTraits {                                                                                                \
        static constexpr const char *ClassName = #JSName;                                                              \
        static constexpr const napi_property_descriptor Properties[] = PropertiesList;                                 \
        static constexpr size_t PropertyCount = sizeof(Properties) / sizeof(napi_property_descriptor);                 \
    }

#define GET_THIS(env, info, this_arg)                                                                                  \
    NAPI_CHECK(napi_get_cb_info(env, info, 0, nullptr, &this_arg, nullptr), "napi_get_cb_info failed.");

#define GET_INFO(env, info, MIN_ARGC, MAX_ARGC)                                                                        \
    size_t argc = (MAX_ARGC);                                                                                          \
    std::array<napi_value, (MAX_ARGC)> argv;                                                                           \
    napi_value this_arg;                                                                                               \
    NAPI_CHECK(napi_get_cb_info(env, info, &argc, argv.data(), &this_arg, nullptr), "napi_get_cb_info failed.");       \
    if (argc < (MIN_ARGC) || argc > (MAX_ARGC)) {                                                                      \
        throw std::runtime_error("invalid arguments count, expected [" #MIN_ARGC ", " #MAX_ARGC "], got " +            \
                                 std::to_string(argc));                                                                \
    }

#define GET_INFO_(env, info, ARGC) GET_INFO(env, info, ARGC, ARGC)

#define UNWRAP(env, this_arg, stream)                                                                                  \
    NAPI_CHECK(napi_unwrap(env, this_arg, reinterpret_cast<void **>(&stream)), "napi_unwrap failed.");

#define NAPI_TRY(env, ClassName, section)                                                                              \
    do {                                                                                                               \
        try {                                                                                                          \
            section;                                                                                                   \
        } catch (const std::exception &e) {                                                                            \
            napi_throw_error(env, ClassName, e.what());                                                                \
            return nullptr;                                                                                            \
        }                                                                                                              \
    } while (0)


template <typename Derived> class StreamBase {
public:
    using Traits = typename Derived::NapiTraits;

    std::shared_ptr<Stream> GetStream() { return stream_; }

    void SetStream(std::shared_ptr<Stream> stream) { stream_ = std::move(stream); }

    static std::shared_ptr<Stream> GetStreamFromNapi(napi_env env, napi_value value) {
        StreamBase *stream;
        UNWRAP(env, value, stream);
        return stream->GetStream().get();
    }

    static napi_value GetCanRead(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);
            auto stream = GetStreamFromNapi(env, this_arg);
            return From<bool>(env, stream->canRead());
        });
    }

    static napi_value GetCanWrite(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);
            auto stream = GetStreamFromNapi(env, this_arg);
            return From<bool>(env, stream->canWrite());
        });
    }

    static napi_value GetCanSeek(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);
            auto stream = GetStreamFromNapi(env, this_arg);
            return From<bool>(env, stream->canSeek());
        });
    }

    static napi_value GetPosition(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);
            auto stream = GetStreamFromNapi(env, this_arg);
            return From<uint32_t>(env, stream->position());
        });
    }

    static napi_value GetLength(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);
            auto stream = GetStreamFromNapi(env, this_arg);
            return From<uint32_t>(env, stream->length());
        });
    }

    static napi_value GetIsClosed(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);
            // 尝试获取 Derived 实例
            Derived *derived = nullptr;
            napi_status status = napi_unwrap(env, this_arg, reinterpret_cast<void **>(&derived));

            // 如果 unwrap 失败或指针为空，认为对象已关闭或无效
            if (status != napi_ok || derived == nullptr) {
                return From<bool>(env, true);
            }

            // 获取包装的 Stream 并查询其状态
            std::shared_ptr<Stream> inner_stream = derived->GetStream();
            // 注意：如果 GetStream() 可能返回空 shared_ptr，则需要检查
            bool is_closed = !inner_stream || inner_stream->isClosed();

            return From<bool>(env, is_closed);
        });
    }

    static napi_value SetLength(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            GET_INFO(env, info, 1, 1);
            auto stream = GetStreamFromNapi(env, this_arg);
            uint32_t length = As<uint32_t>(env, argv[0]);
            stream->setLength(length);
            return nullptr;
        });
    }
    static napi_value Read(napi_env env, napi_callback_info info) {
        struct ReadContext {
            NapiBuffer buffer;
            std::shared_ptr<Stream> stream;
            long result;
        };
        NAPI_TRY(env, Traits::ClassName, {
            GET_INFO(env, info, 1, 2);
            auto stream = GetStreamFromNapi(env, this_arg);
            ReadContext *context;
            std::unique_ptr<AsyncWorker> worker = AsyncWorker::New<ReadContext>("read_async", &context);
            context->stream = stream;
            NapiBuffer buffer(env, argv[0]);
            if (argc == 2) {
                NapiObject obj(env, argv[1]);
                long offset = obj.get<long>("offset", 0);
                if (offset > buffer.size())
                    StreamError::ThrowInvalidOffset();
                size_t length = obj.get<size_t>("length", buffer.size() - offset);
                if (length + offset > buffer.size())
                    StreamError::ThrowInvalidCount();
                buffer.sub(offset, length);
            }
            context->buffer = std::move(buffer);
            worker->set_execute([](napi_env env, void *data) {
                ReadContext *context = static_cast<ReadContext *>(data);
                context->result = context->stream->read(context->buffer.data(), 0, context->buffer.size());
            });
            worker->set_ok([](napi_env env, void *data) {
                ReadContext *context = static_cast<ReadContext *>(data);
                return From<long>(env, context->result);
            });
            napi_value promise = worker->Create(env);
            worker.release();
            return promise;
        });
    }

    static napi_value ReadSync(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            GET_INFO(env, info, 1, 2);
            auto stream = GetStreamFromNapi(env, this_arg);
            void *buf = nullptr;
            size_t len = 0;
            long off = 0;
            GetBuffer(env, argv[0], &buf, &len);
            if (argc == 2) {
                NapiObject obj(env, argv[1]);
                long offset = obj.get<long>("offset", 0);
                if (offset > len)
                    StreamError::ThrowInvalidOffset();
                size_t length = obj.get<size_t>("length", len - offset);
                if (length + offset > len)
                    StreamError::ThrowInvalidCount();
                len = length;
                off = offset;
            }
            long value = stream->read(buf, off, len);
            return From<long>(env, value);
        });
    }

    static napi_value Write(napi_env env, napi_callback_info info) {
        struct WriteContext {
            NapiBuffer buffer;
            std::shared_ptr<Stream> stream;
        };
        NAPI_TRY(env, Traits::ClassName, {
            GET_INFO(env, info, 1, 2);
            auto stream = GetStreamFromNapi(env, this_arg);
            WriteContext *context;
            std::unique_ptr<AsyncWorker> worker = AsyncWorker::New<WriteContext>("write_async", &context);
            context->stream = stream;
            NapiBuffer buffer(env, argv[0]);
            if (argc == 2) {
                NapiObject obj(env, argv[1]);
                long offset = obj.get<long>("offset", 0);
                if (offset > buffer.size())
                    StreamError::ThrowInvalidOffset();
                size_t length = obj.get<size_t>("length", buffer.size() - offset);
                if (length + offset > buffer.size())
                    StreamError::ThrowInvalidCount();
                buffer.sub(offset, length);
            }
            context->buffer = std::move(buffer);
            worker->set_execute([](napi_env env, void *data) {
                WriteContext *context = static_cast<WriteContext *>(data);
                context->stream->write(context->buffer.data(), 0, context->buffer.size());
            });

            napi_value promise = worker->Create(env);
            worker.release();
            return promise;
        });
    }

    static napi_value WriteSync(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            GET_INFO(env, info, 1, 2);
            auto stream = GetStreamFromNapi(env, this_arg);
            void *buf = nullptr;
            size_t len = 0;
            long off = 0;
            GetBuffer(env, argv[0], &buf, &len);
            if (argc == 2) {
                NapiObject obj(env, argv[1]);
                long offset = obj.get<long>("offset", 0);
                if (offset > len)
                    StreamError::ThrowInvalidOffset();
                size_t length = obj.get<size_t>("length", len - offset);
                if (length + offset > len)
                    StreamError::ThrowInvalidCount();
                len = length;
                off = offset;
            }
            stream->write(buf, off, len);
            return nullptr;
        });
    }

    static napi_value Seek(napi_env env, napi_callback_info info) {
        struct SeekContext {
            std::shared_ptr<Stream> stream;
            long offset;
            Stream::SeekOrigin origin;
            size_t result;
        };
        NAPI_TRY(env, Traits::ClassName, {
            GET_INFO_(env, info, 2);
            auto stream = GetStreamFromNapi(env, this_arg);
            SeekContext *context;
            std::unique_ptr<AsyncWorker> worker = AsyncWorker::New<SeekContext>("seek_async", &context);
            context->stream = stream;
            context->offset = As<long>(env, argv[0]);
            context->origin = static_cast<Stream::SeekOrigin>(As<int32_t>(env, argv[1]));
            worker->set_execute([](napi_env env, void *data) {
                SeekContext *context = static_cast<SeekContext *>(data);
                context->result = context->stream->seek(context->offset, context->origin);
            });
            worker->set_ok([](napi_env env, void *data) -> napi_value {
                SeekContext *context = static_cast<SeekContext *>(data);
                return From<size_t>(env, context->result);
            });

            napi_value promise = worker->Create(env);
            worker.release();
            return promise;
        });
    }

    static napi_value SeekSync(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            GET_INFO_(env, info, 2);
            auto stream = GetStreamFromNapi(env, this_arg);
            long offset = As<long>(env, argv[0]);
            Stream::SeekOrigin origin = static_cast<Stream::SeekOrigin>(As<int32_t>(env, argv[1]));
            size_t value = stream->seek(offset, origin);
            return From<size_t>(env, value);
        });
    }

    static napi_value Flush(napi_env env, napi_callback_info info) {
        struct FlushContext {
            std::shared_ptr<Stream> stream;
        };
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);
            auto stream = GetStreamFromNapi(env, this_arg);
            FlushContext *context;
            std::unique_ptr<AsyncWorker> worker = AsyncWorker::New<FlushContext>("flush_async", &context);
            context->stream = std::move(stream);
            worker->set_execute([](napi_env env, void *data) {
                FlushContext *context = static_cast<FlushContext *>(data);
                context->stream->flush();
            });
            napi_value promise = worker->Create(env);
            worker.release();
            return promise;
        });
    }

    static napi_value FlushSync(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);
            auto stream = GetStreamFromNapi(env, this_arg);
            stream->flush();
            return nullptr;
        });
    }

    static napi_value CopyTo(napi_env env, napi_callback_info info) {
        struct CopyToContext {
            std::shared_ptr<Stream> stream;
            std::shared_ptr<Stream> target;
            size_t bufsize;
        };
        NAPI_TRY(env, Traits::ClassName, {
            GET_INFO(env, info, 1, 2);
            CopyToContext *context;
            std::unique_ptr<AsyncWorker> worker = AsyncWorker::New<CopyToContext>("copyTo_async", &context);
            context->stream = std::move(GetStreamFromNapi(env, this_arg));
            context->target = std::move(GetStreamFromNapi(env, argv[0]));
            context->bufsize = As<size_t>(env, argv[1], -1);
            worker->set_execute([](napi_env env, void *data) {
                CopyToContext *context;
                context->stream->copyTo(context->target, context->bufsize);
            });
            napi_value promise = worker->Create(env);
            worker.release();
            return promise;
        });
    }

    static napi_value CopyToSync(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            GET_INFO(env, info, 1, 2);
            auto stream = GetStreamFromNapi(env, this_arg);
            auto target = GetStreamFromNapi(env, argv[0]);
            size_t bufsize = As<size_t>(env, argv[1], -1);

            stream->copyTo(target, bufsize);
            return nullptr;
        });
    }

    static napi_value Close(napi_env env, napi_callback_info info) {
        struct CloseContext {
            napi_ref ref;
            std::shared_ptr<Stream> stream;
        };
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);

            // 尝试获取 Derived 实例
            Derived *derived = nullptr;
            napi_status status = napi_unwrap(env, this_arg, reinterpret_cast<void **>(&derived));

            // 如果 unwrap 失败或指针为空，认为对象已关闭或无效
            if (status != napi_ok || derived == nullptr) {
                return nullptr;
            }

            CloseContext *context;
            std::unique_ptr<AsyncWorker> worker = AsyncWorker::New<CloseContext>("close_async", &context);

            context->stream = derived->GetStream();
            napi_create_reference(env, this_arg, 1, &context->ref);

            worker->set_execute([](napi_env env, void *data) {
                CloseContext *context = static_cast<CloseContext *>(data);
                if (context->stream && !context->stream->isClosed())
                    context->stream->close();
            });
            worker->set_ok([](napi_env env, void *data) {
                CloseContext *context = static_cast<CloseContext *>(data);
                napi_value this_arg;
                void *result;
                napi_get_reference_value(env, context->ref, &this_arg);
                napi_remove_wrap(env, this_arg, &result);
                napi_delete_reference(env, context->ref);
            });
            napi_value promise = worker->Create(env);
            worker.release();
            return promise;
        });
    }

    static napi_value CloseSync(napi_env env, napi_callback_info info) {
        NAPI_TRY(env, Traits::ClassName, {
            napi_value this_arg;
            GET_THIS(env, info, this_arg);
            // 尝试获取 Derived 实例
            Derived *derived = nullptr;
            napi_status status = napi_unwrap(env, this_arg, reinterpret_cast<void **>(&derived));

            // 如果 unwrap 失败或指针为空，认为对象已关闭或无效
            if (status != napi_ok || derived == nullptr) {
                return nullptr;
            }

            // 获取包装的 Stream 并查询其状态
            std::shared_ptr<Stream> inner_stream = derived->GetStream();
            // 注意：如果 GetStream() 可能返回空 shared_ptr，则需要检查
            if (inner_stream && !inner_stream->isClosed()) {
                inner_stream->close();
            }

            napi_remove_wrap(env, this_arg, reinterpret_cast<void **>(&derived));
            return nullptr;
        });
    }

protected:
    std::shared_ptr<Stream> stream_;
};

#endif // STREAMCORE_STREAM_BASE_H
